﻿namespace TNet
{
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Net;
    using System.Net.Sockets;
    using System.Runtime.InteropServices;
    using System.Threading;

    public class TcpProtocol : Player
    {
        private const int CONNECT_TIMEOUT = 0x4e20;
        public long lastReceivedTime;
        private StateObject mBeginSendState = new StateObject();
        private TNet.Buffer mBuffer;
        private TNet.List<Socket> mConnecting = new TNet.List<Socket>();
        private int mExpected;
        private IPEndPoint mFallback;
        private Queue<TNet.Buffer> mIn = new Queue<TNet.Buffer>();
        private bool mNoDelay;
        private int mOffset;
        private Queue<TNet.Buffer> mOut = new Queue<TNet.Buffer>();
        private TNet.Buffer mReceiveBuffer;
        private Socket mSocket;
        private Stage mStage;
        private byte[] mTemp = new byte[0x2000];
        public const long PING_TIME = 0xfa0L;
        public IPEndPoint tcpEndPoint;
        public long timeoutTime = 0xea60L;
        public const long USER_CONNECT_TIMEOUT = 0x7530L;

        public BinaryWriter BeginSend(byte packetID)
        {
            this.mBuffer = TNet.Buffer.Create(false);
            return this.mBuffer.BeginPacket(packetID);
        }

        public BinaryWriter BeginSend(Packet type)
        {
            this.mBuffer = TNet.Buffer.Create(false);
            return this.mBuffer.BeginPacket(type);
        }

        private void CancelConnect(object obj)
        {
            IAsyncResult result = (IAsyncResult) obj;
            if ((result != null) && !result.AsyncWaitHandle.WaitOne(0x4e20, true))
            {
                try
                {
                    Socket asyncState = (Socket) result.AsyncState;
                    if (asyncState != null)
                    {
                        asyncState.Close();
                        TNet.List<Socket> mConnecting = this.mConnecting;
                        lock (mConnecting)
                        {
                            if ((this.mConnecting.size > 0) && (this.mConnecting[this.mConnecting.size - 1] == asyncState))
                            {
                                this.mSocket = null;
                                if (!this.ConnectToFallback())
                                {
                                    this.Error("Unable to connect");
                                    this.Close(false);
                                }
                            }
                            this.mConnecting.Remove(asyncState);
                        }
                    }
                }
                catch (Exception)
                {
                }
            }
        }

        public void Close(bool notify)
        {
            this.mStage = Stage.NotConnected;
            base.name = "Guest";
            base.data = null;
            if (this.mReceiveBuffer != null)
            {
                this.mReceiveBuffer.Recycle();
                this.mReceiveBuffer = null;
            }
            if (this.mSocket != null)
            {
                try
                {
                    if (this.mSocket.Connected)
                    {
                        this.mSocket.Shutdown(SocketShutdown.Both);
                    }
                    this.mSocket.Close();
                }
                catch (Exception)
                {
                }
                this.mSocket = null;
                if (notify)
                {
                    TNet.Buffer item = TNet.Buffer.Create();
                    item.BeginPacket(Packet.Disconnect);
                    item.EndTcpPacketWithOffset(4);
                    Queue<TNet.Buffer> mIn = this.mIn;
                    lock (mIn)
                    {
                        this.mIn.Enqueue(item);
                    }
                }
            }
        }

        public void Connect(IPEndPoint externalIP)
        {
            this.Connect(externalIP, null);
        }

        public void Connect(IPEndPoint externalIP, IPEndPoint internalIP)
        {
            this.Disconnect();
            TNet.Buffer.Recycle(this.mIn);
            TNet.Buffer.Recycle(this.mOut);
            if ((internalIP != null) && (Tools.GetSubnet(Tools.localAddress) == Tools.GetSubnet(internalIP.Address)))
            {
                this.tcpEndPoint = internalIP;
                this.mFallback = externalIP;
            }
            else
            {
                this.tcpEndPoint = externalIP;
                this.mFallback = internalIP;
            }
            this.ConnectToTcpEndPoint();
        }

        private bool ConnectToFallback()
        {
            this.tcpEndPoint = this.mFallback;
            this.mFallback = null;
            return ((this.tcpEndPoint != null) && this.ConnectToTcpEndPoint());
        }

        private bool ConnectToTcpEndPoint()
        {
            if (this.tcpEndPoint != null)
            {
                this.mStage = Stage.Connecting;
                try
                {
                    TNet.List<Socket> mConnecting = this.mConnecting;
                    lock (mConnecting)
                    {
                        this.mSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                        this.mConnecting.Add(this.mSocket);
                    }
                    IAsyncResult parameter = this.mSocket.BeginConnect(this.tcpEndPoint, new AsyncCallback(this.OnConnectResult), this.mSocket);
                    new Thread(new ParameterizedThreadStart(this.CancelConnect)).Start(parameter);
                    return true;
                }
                catch (Exception exception)
                {
                    this.Error(exception.Message);
                }
            }
            else
            {
                this.Error("Unable to resolve the specified address");
            }
            return false;
        }

        public void Disconnect()
        {
            this.Disconnect(false);
        }

        public void Disconnect(bool notify)
        {
            if (this.isConnected)
            {
                try
                {
                    TNet.List<Socket> mConnecting = this.mConnecting;
                    lock (mConnecting)
                    {
                        int size = this.mConnecting.size;
                        while (size > 0)
                        {
                            Socket socket = this.mConnecting[--size];
                            this.mConnecting.RemoveAt(size);
                            if (socket != null)
                            {
                                socket.Close();
                            }
                        }
                    }
                    if (this.mSocket != null)
                    {
                        this.Close(notify || this.mSocket.Connected);
                    }
                }
                catch (Exception)
                {
                    TNet.List<Socket> list2 = this.mConnecting;
                    lock (list2)
                    {
                        this.mConnecting.Clear();
                    }
                    this.mSocket = null;
                }
            }
        }

        public void EndSend()
        {
            this.mBuffer.EndPacket();
            this.SendTcpPacket(this.mBuffer);
            this.mBuffer = null;
        }

        public void Error(string error)
        {
            this.Error(TNet.Buffer.Create(), error);
        }

        private void Error(TNet.Buffer buffer, string error)
        {
            buffer.BeginPacket(Packet.Error).Write(error);
            buffer.EndTcpPacketWithOffset(4);
            Queue<TNet.Buffer> mIn = this.mIn;
            lock (mIn)
            {
                this.mIn.Enqueue(buffer);
            }
        }

        private void OnConnectResult(IAsyncResult result)
        {
            Socket asyncState = (Socket) result.AsyncState;
            if (asyncState != null)
            {
                if ((this.mSocket != null) && (asyncState == this.mSocket))
                {
                    bool flag = true;
                    string error = "Failed to connect";
                    try
                    {
                        asyncState.EndConnect(result);
                    }
                    catch (Exception exception)
                    {
                        if (asyncState == this.mSocket)
                        {
                            this.mSocket = null;
                        }
                        asyncState.Close();
                        error = exception.Message;
                        flag = false;
                    }
                    if (flag)
                    {
                        this.mStage = Stage.Verifying;
                        BinaryWriter bw = this.BeginSend(Packet.RequestID);
                        bw.Write(12);
                        bw.Write(!string.IsNullOrEmpty(base.name) ? base.name : "Guest");
                        bw.WriteObject(base.data);
                        this.EndSend();
                        this.StartReceiving();
                    }
                    else if (!this.ConnectToFallback())
                    {
                        this.Error(error);
                        this.Close(false);
                    }
                }
                TNet.List<Socket> mConnecting = this.mConnecting;
                lock (mConnecting)
                {
                    this.mConnecting.Remove(asyncState);
                }
            }
        }

        private void OnReceive(IAsyncResult result)
        {
            if (this.mStage != Stage.NotConnected)
            {
                int bytes = 0;
                Socket asyncState = (Socket) result.AsyncState;
                try
                {
                    bytes = asyncState.EndReceive(result);
                    if (asyncState != this.mSocket)
                    {
                        return;
                    }
                }
                catch (Exception exception)
                {
                    if (asyncState == this.mSocket)
                    {
                        this.Error(exception.Message);
                        this.Disconnect(true);
                    }
                    return;
                }
                this.lastReceivedTime = DateTime.UtcNow.Ticks / 0x2710L;
                if (bytes == 0)
                {
                    this.Close(true);
                }
                else if (this.ProcessBuffer(bytes))
                {
                    if (this.mStage != Stage.NotConnected)
                    {
                        try
                        {
                            this.mSocket.BeginReceive(this.mTemp, 0, this.mTemp.Length, SocketFlags.None, new AsyncCallback(this.OnReceive), this.mSocket);
                        }
                        catch (Exception exception2)
                        {
                            this.Error(exception2.Message);
                            this.Close(false);
                        }
                    }
                }
                else
                {
                    this.Close(true);
                }
            }
        }

        private void OnSend(IAsyncResult result)
        {
            if (this.mStage != Stage.NotConnected)
            {
                int num = 0;
                try
                {
                    num = this.mSocket.EndSend(result);
                }
                catch (Exception exception)
                {
                    num = 0;
                    this.Close(true);
                    this.Error(exception.Message);
                    return;
                }
                Queue<TNet.Buffer> mOut = this.mOut;
                lock (mOut)
                {
                    if ((this.mSocket != null) && this.mSocket.Connected)
                    {
                        TNet.Buffer buffer = null;
                        if (this.mOut.Count == 0)
                        {
                        }
                        TNet.Buffer buffer2 = this.mOut.Peek();
                        if (buffer2.size == num)
                        {
                            this.mOut.Dequeue().Recycle();
                            buffer = (this.mOut.Count != 0) ? this.mOut.Peek() : null;
                        }
                        else if (buffer2.size > num)
                        {
                            buffer2.position += num;
                            buffer = buffer2;
                        }
                        else
                        {
                            this.mOut.Dequeue().Recycle();
                            buffer = (this.mOut.Count != 0) ? this.mOut.Peek() : null;
                        }
                        if (buffer != null)
                        {
                            try
                            {
                                this.mBeginSendState.workSocket = this.mSocket;
                                this.mSocket.BeginSend(buffer.buffer, buffer.position, buffer.size, SocketFlags.None, new AsyncCallback(this.OnSend), this.mBeginSendState);
                            }
                            catch (Exception exception2)
                            {
                                this.Error(exception2.Message);
                                this.Close(false);
                            }
                        }
                    }
                    else
                    {
                        this.Close(true);
                    }
                }
            }
        }

        private bool ProcessBuffer(int bytes)
        {
            if (this.mReceiveBuffer == null)
            {
                this.mReceiveBuffer = TNet.Buffer.Create();
                this.mReceiveBuffer.BeginWriting(false).Write(this.mTemp, 0, bytes);
                this.mExpected = 0;
                this.mOffset = 0;
            }
            else
            {
                this.mReceiveBuffer.BeginWriting(true).Write(this.mTemp, 0, bytes);
            }
            int num = this.mReceiveBuffer.size - this.mOffset;
            while (num >= 4)
            {
                if (this.mExpected == 0)
                {
                    this.mExpected = this.mReceiveBuffer.PeekInt(this.mOffset);
                    if ((this.mExpected < 0) || (this.mExpected > 0x1000000))
                    {
                        this.Close(true);
                        return false;
                    }
                }
                num -= 4;
                if (num == this.mExpected)
                {
                    this.mReceiveBuffer.BeginReading(this.mOffset + 4);
                    Queue<TNet.Buffer> queue = this.mIn;
                    lock (queue)
                    {
                        this.mIn.Enqueue(this.mReceiveBuffer);
                    }
                    this.mReceiveBuffer = null;
                    this.mExpected = 0;
                    this.mOffset = 0;
                    break;
                }
                if (num <= this.mExpected)
                {
                    break;
                }
                int count = this.mExpected + 4;
                TNet.Buffer item = TNet.Buffer.Create();
                item.BeginWriting(false).Write(this.mReceiveBuffer.buffer, this.mOffset, count);
                item.BeginReading(4);
                Queue<TNet.Buffer> mIn = this.mIn;
                lock (mIn)
                {
                    this.mIn.Enqueue(item);
                }
                num -= this.mExpected;
                this.mOffset += count;
                this.mExpected = 0;
            }
            return true;
        }

        public bool ReceivePacket(out TNet.Buffer buffer)
        {
            if (this.mIn.Count != 0)
            {
                Queue<TNet.Buffer> mIn = this.mIn;
                lock (mIn)
                {
                    buffer = this.mIn.Dequeue();
                    return true;
                }
            }
            buffer = null;
            return false;
        }

        public void Release()
        {
            this.Close(false);
            TNet.Buffer.Recycle(this.mIn);
            TNet.Buffer.Recycle(this.mOut);
        }

        public void SendTcpPacket(TNet.Buffer origBuffer)
        {
            TNet.Buffer item = origBuffer;
            item.MarkAsUsed();
            if ((this.mSocket != null) && this.mSocket.Connected)
            {
                item.BeginReading();
                if (item.size == 0)
                {
                    item.Recycle();
                }
                else
                {
                    Queue<TNet.Buffer> mOut = this.mOut;
                    lock (mOut)
                    {
                        this.mOut.Enqueue(item);
                        if (this.mOut.Count == 1)
                        {
                            try
                            {
                                this.mBeginSendState.workSocket = this.mSocket;
                                this.mSocket.BeginSend(item.buffer, item.position, item.size, SocketFlags.None, new AsyncCallback(this.OnSend), this.mBeginSendState);
                            }
                            catch (Exception exception)
                            {
                                this.Error(exception.Message);
                                this.Close(false);
                                this.Release();
                            }
                        }
                    }
                }
            }
            else
            {
                item.Recycle();
            }
        }

        public void StartReceiving()
        {
            this.StartReceiving(null);
        }

        public void StartReceiving(Socket socket)
        {
            if (socket != null)
            {
                this.Close(false);
                this.mSocket = socket;
            }
            if ((this.mSocket != null) && this.mSocket.Connected)
            {
                this.mStage = Stage.Verifying;
                this.lastReceivedTime = DateTime.UtcNow.Ticks / 0x2710L;
                this.tcpEndPoint = (IPEndPoint) this.mSocket.RemoteEndPoint;
                try
                {
                    this.mSocket.BeginReceive(this.mTemp, 0, this.mTemp.Length, SocketFlags.None, new AsyncCallback(this.OnReceive), this.mSocket);
                }
                catch (Exception exception)
                {
                    this.Error(exception.Message);
                    this.Disconnect(true);
                }
            }
        }

        public bool VerifyRequestID(TNet.Buffer buffer, bool uniqueID)
        {
            BinaryReader reader = buffer.BeginReading();
            if (reader.ReadByte() == 3)
            {
                if (reader.ReadInt32() == 12)
                {
                    object mLock = Player.mLock;
                    lock (mLock)
                    {
                        base.id = !uniqueID ? 0 : ++Player.mPlayerCounter;
                    }
                    base.name = reader.ReadString();
                    if (buffer.size > 1)
                    {
                        base.data = reader.ReadObject();
                    }
                    else
                    {
                        base.data = null;
                    }
                    this.mStage = Stage.Connected;
                    BinaryWriter writer = this.BeginSend(Packet.ResponseID);
                    writer.Write(12);
                    writer.Write(base.id);
                    writer.Write((long) (DateTime.UtcNow.Ticks / 0x2710L));
                    this.EndSend();
                    return true;
                }
                this.BeginSend(Packet.ResponseID).Write(0);
                this.EndSend();
                this.Close(false);
            }
            return false;
        }

        public bool VerifyResponseID(Packet packet, BinaryReader reader)
        {
            if (packet == Packet.ResponseID)
            {
                int num = reader.ReadInt32();
                if ((num != 0) && (num == 12))
                {
                    base.id = reader.ReadInt32();
                    this.mStage = Stage.Connected;
                    return true;
                }
                base.id = 0;
                this.Error("Version mismatch! Server is running a different protocol version!");
                this.Close(false);
                return false;
            }
            this.Error("Expected a response ID, got " + packet);
            this.Close(false);
            return false;
        }

        public string address
        {
            get
            {
                return ((this.tcpEndPoint == null) ? "0.0.0.0:0" : this.tcpEndPoint.ToString());
            }
        }

        public bool isConnected
        {
            get
            {
                return (this.mStage == Stage.Connected);
            }
        }

        public bool isTryingToConnect
        {
            get
            {
                return (this.mConnecting.size != 0);
            }
        }

        public bool noDelay
        {
            get
            {
                return this.mNoDelay;
            }
            set
            {
                if (this.mNoDelay != value)
                {
                    this.mNoDelay = value;
                    this.mSocket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.Debug, this.mNoDelay);
                }
            }
        }

        public Stage stage
        {
            get
            {
                return this.mStage;
            }
        }

        public enum Stage
        {
            NotConnected,
            Connecting,
            Verifying,
            Connected
        }

        private class StateObject
        {
            public Socket workSocket;
        }
    }
}

